﻿using System;
using System.Collections.Concurrent;
using System.Threading;

namespace Utils
{
	public abstract class MessageProcessor : IDisposable
	{
		private readonly string _threadName;

		#region Attributes

		private Thread _thread;
		private readonly object _startLock;
		private readonly ManualResetEvent _stopLock;

		#endregion

		#region Properties

		public bool Stopped { get; private set; }

		protected Message CurrentMessage { get; set; }
		protected BlockingCollection<Message> Messages { get; private set; }

		#endregion

		#region Public Methods

		protected MessageProcessor(string threadName)
		{
			_startLock = new object();
			_stopLock = new ManualResetEvent(false);

			_threadName = threadName;
		}

		public virtual void Start()
		{
			lock (_startLock)
			{
				this.Messages = new BlockingCollection<Message>();

				_stopLock.Reset();

				_thread = new Thread(this.MainMethod);
				_thread.Name = _threadName;
				_thread.Start();
			}
		}

		public virtual void Stop()
		{
			this.Messages.Add(new Message(MessageType.Stop));
		}

		public void AddMessage(Message message)
		{
			this.Messages.Add(message);
		}

		protected abstract void ProcessMessage();

		protected virtual void MainMethod()
		{
			lock (_startLock)
			{
				this.Stopped = false;

				do
				{
					this.CurrentMessage = this.Messages.Take();
					if (this.CurrentMessage.Type != MessageType.Stop)
					{
						this.ProcessMessage();
					}
				} while (this.CurrentMessage.Type != MessageType.Stop);

				// Re-initialize the collection
				this.Messages.Dispose();
				this.Messages = new BlockingCollection<Message>();

				_thread = null;
				
				this.Stopped = true;

				_stopLock.Set();
			}
		}

		#endregion

		#region IDisposable

		protected bool Disposed { get; private set; }

		private void Dispose(bool disposing)
		{
			if (!this.Disposed)
			{
				this.InternalDispose(disposing);
			}

			this.Disposed = true;
		}

		protected virtual void InternalDispose(bool disposing)
		{
			if (disposing)
			{
				if (!this.Stopped)
				{
					this.Stop();
				}

				_stopLock.WaitOne();
				this.Messages.Dispose();
			}

			// Dispose here only unmanaged objects 
			// Don’t use managed objects here because maybe 
			// they have been finalized already
		}

		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		~MessageProcessor()
		{
			this.Dispose(false);
		}

		#endregion
	}
}
